﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
using System.Xml.Schema;
using Interfaces;

namespace Pozr
{
    public class PositionHandler : IPositionHandler
    {
        private Dictionary<PositionOriginator, OriginatorModule> modules = new Dictionary<PositionOriginator,OriginatorModule>();
        private int centerMarkerId = -1;

        static System.Text.ASCIIEncoding ByteEncoding = new System.Text.ASCIIEncoding();
        private int cacheInterval = 10;

        public void WriteDebugLog(string s)
        {
            Middleware.OnLogEvent(s);
        }

        public string WorkingDirectory
        {
            get { return Middleware.ApplicationDirectory; }
        }

        /* Temporary code +++ USE THIS LOGIC FOR THE DATABASE DRIVEN VERSION!
         * 
        private PairedTable pairedTable;

        [Serializable()]
        public class PairedRecord
        {
            public class Measurement
            {
                public IAbsoluteData Position { get; set; }
                public int SignalIndicator { get; set; }

                public Measurement()
                {
                    Position = null;
                    SignalIndicator = -1;
                }

                public Measurement(IAbsoluteData _absoluteData, int _signalIndicator)
                {
                    Position = _absoluteData;
                    SignalIndicator = _signalIndicator;
                }

                public static Measurement Empty
                {
                    get { return new Measurement(); }
                }


                public bool IsEmpty()
                {
                    return (SignalIndicator >= 0);
                }
            }

            public IProximityData Signal { get; private set; }
            public Measurement Border { get; private set; }
            public Measurement Center { get; private set; }

            /// <summary>
            /// Indicates that the data sholud be send to the server
            /// </summary>
            public bool Fresh { get; private set; }

            public PairedRecord()
            {
                Signal = null;
                Border = Measurement.Empty;
                Center = Measurement.Empty;
            }

            public PairedRecord(IProximityData _signal, Measurement _border, Measurement _center)
            {
                Signal = _signal;
                Border = _border;
                Center = _center;
                Fresh = true;
            }

            public bool UpdateCenter(IProximityData _proximityData, IAbsoluteData _center)
            {
                if (_proximityData.RelativeSignalStrength > Center.SignalIndicator)
                {
                    if (Center.IsEmpty())
                    {
                        Center = new Measurement(_center, _proximityData.RelativeSignalStrength);
                        return true;
                    }
                    else
                    {
                        Center.Position = _center;
                        Center.SignalIndicator = _proximityData.RelativeSignalStrength;
                        Fresh = true;
                        return true;
                    }
                }
                else
                {
                    return false;
                }
            }

            public bool UpdateBorder(IProximityData _proximityData, IAbsoluteData _border)
            {
                if (_proximityData.RelativeSignalStrength < Border.SignalIndicator)
                {
                    if (Border.IsEmpty())
                    {
                        Border = new Measurement(_border, _proximityData.RelativeSignalStrength);
                        return true;
                    }
                    else
                    {
                        Border.Position = _border;
                        Border.SignalIndicator = _proximityData.RelativeSignalStrength;
                        Fresh = true;
                        return true;
                    }
                }
                else
                {
                    return false;
                }
            }
        }

        // Warning: limited thread safety (TOTHINK: is it enough?)
        [Serializable()]
        public class PairedTable
        {
            private List<PairedRecord> PairedList;

            public PairedTable()
            {
                PairedList = new List<PairedRecord>();
            }

            public bool Contains(IProximityData _proximityData)
            {
                lock (PairedList)
                {
                    foreach (PairedRecord record in PairedList)
                    {
                        if (record.Signal.HashValue == _proximityData.HashValue)
                        {
                            return true;
                        }
                    }
                }
                return false;
            }

            public bool UpdateCenter(IProximityData _proximityData, IAbsoluteData _center)
            {
                lock (PairedList)
                {
                    foreach (PairedRecord record in PairedList)
                    {
                        if (record.Signal.HashValue == _proximityData.HashValue)
                        {
                            return record.UpdateCenter(_proximityData, _center);
                        }
                    }
                    PairedList.Add(new PairedRecord(_proximityData, PairedRecord.Measurement.Empty, new PairedRecord.Measurement(_center, _proximityData.RelativeSignalStrength)));
                    return true;
                }
            }

            public bool UpdateBorder(IProximityData _proximityData, IAbsoluteData _border)
            {
                lock (PairedList)
                {
                    foreach (PairedRecord record in PairedList)
                    {
                        if (record.Signal.HashValue == _proximityData.HashValue)
                        {
                            return record.UpdateBorder(_proximityData, _border);
                        }
                    }
                    PairedList.Add(new PairedRecord(_proximityData, new PairedRecord.Measurement(_border, _proximityData.RelativeSignalStrength), PairedRecord.Measurement.Empty));
                    return true;
                }
            }
        }
        */

        protected static class AbsolutePositionCache
        {
            static IAbsoluteData cache;
            static DateTime time;

            public static float Latitude
            {
                get { return Convert.ToSingle(cache.Latitude); }
            }

            public static float Longitude
            {
                get { return Convert.ToSingle(cache.Longitude); }
            }

            public static void Update(IAbsoluteData data)
            {
                cache = data;
                time = DateTime.Now;
            }

            public static IAbsoluteData GetData(int validityInterval)
            {
                if (time.Add(TimeSpan.FromSeconds(validityInterval)) > DateTime.Now)
                {
                    return cache;
                }
                return null;
            }
        }

        public bool SetRadioState(PositionOriginator type, bool state)
        {
            if (modules.ContainsKey(type))
            {
                if (state)
                {
                    modules[type].Instance.SetRadioOn();
                    Middleware.WriteLogToFile(modules[type].Instance.Name + " module turned on.");
                }
                else
                {
                    modules[type].Instance.SetRadioOff();
                    Middleware.WriteLogToFile(modules[type].Instance.Name + " module turned off.");
                }
                return true;
            }
            return false;
        }

        public bool GetRadioState(PositionOriginator type)
        {
            if(modules.ContainsKey(type))
            {
                return modules[type].Instance.GetRadioStatus();
            }
            else
            {
                return false;
            }
        }

        public bool RequestData(PositionOriginator positionOriginator)
        {
            if (modules.ContainsKey(positionOriginator))
            {
                modules[positionOriginator].Instance.DataRequest();
                return true;
            }

            Middleware.WriteLogToFile("Invalid request!");
            return false;
        }

        /// <summary>
        /// Data class for position originator plugin.
        /// Holds and instance of the loaded plugin, as well as the Plugin's assembly path
        /// </summary>
        public class OriginatorModule
        {
            //This is the actual Plugin object.. 
            //Holds an instance of the plugin to access
            //ALso holds assembly path... not really necessary
            private IPositionOriginator myInstance = null;
            private string myAssemblyPath = "";
            private bool validPlugin;

            public bool IsValid
            {
                get { return validPlugin; }
            }

            public OriginatorModule()
            {
                validPlugin = true;
            }

            public OriginatorModule(bool valid)
            {
                validPlugin = valid;
            }

            public IPositionOriginator Instance
            {
                get { return myInstance; }
                set { myInstance = value; }
            }
            public string AssemblyPath
            {
                get { return myAssemblyPath; }
                set { myAssemblyPath = value; }
            }
        }

        public PositionHandler()
        {
            Middleware.Settings.InitSettingsGroup("PositionHandler");
            Middleware.Settings.InitSettingsGroup("Autostart");

            try
            {
                object temp = Middleware.Settings.GetSetting("PositionHandler", "GpsCacheInterval");
                if (temp == null)
                {
                    Middleware.Settings.SetSetting("PositionHandler", "GpsCacheInterval", 15); // default value
                }
                else
                {
                    cacheInterval = int.Parse(temp.ToString());
                }
            }
            catch (Exception e)
            {
                Middleware.WriteLogToFile(e.Message);
            }
        }

        public void LoadModules()
        {
            LoadModules(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) + "\\modules");
        }

        public void LoadModules(string path)
        {
            modules.Clear();

            //Go through all the files in the modules directory
            try
            {
                foreach (string moduleDir in Directory.GetDirectories(path))
                {
                    DirectoryInfo dir = new DirectoryInfo(moduleDir);

                    string fileOn = moduleDir + "\\" + dir.Name + ".dll";
                    FileInfo file = new FileInfo(fileOn);

                    if (Middleware.Settings.GetSetting("Autostart", dir.Name) != null)
                    {
                        if (file.Exists && (bool)(Middleware.Settings.GetSetting("Autostart", dir.Name)))
                        {
                            //Add the module
                            OriginatorModule module = this.AddPositionOriginator(fileOn);

                            if (module.IsValid)
                            {
                                module.Instance.RequestedDataAvailable += new RequestedDataAvailableHandler(this.RequestedDataAvailableHandler);
                                module.Instance.RadioSwitchedOnEvent += new RadioSwitchedOnEventHandler(this.RadioSwitchedOnEventHandler);
                                module.Instance.RadioSwitchedOffEvent += new RadioSwitchedOffEventHandler(this.RadioSwitchedOffEventHandler);

                                if (module.Instance is IProximityOriginator)
                                {
                                    module.Instance.NewSignalEvent += new NewSignalEventHandler(this.NewSignalEventHandler);
                                    module.Instance.BestSignalEvent += new BestSignalEventHandler(this.BestSignalEventHandler);
                                }
                                else if (module.Instance is IAbsoluteOriginator)
                                {
                                    module.Instance.NewSignalEvent += new NewSignalEventHandler(this.NewGpsSignalEventHandler);
                                    module.Instance.BestSignalEvent += new BestSignalEventHandler(this.BestGpsSignalEventHandler);
                                }

                                modules.Add(module.Instance.GetType(), module);
                                module.Instance.SetRadioOn();
                                Middleware.WriteLogToFile(module.Instance.Name + " module loaded.");

                                Middleware.mainFrm.splash_displayStatusMessage(module.Instance.Name + " loaded.");
                                Middleware.mainFrm.splash_displayModuleIcon(module.Instance.IconEnabled);
                            }
                            else
                            {
                                Middleware.WriteLogToFile(fileOn + " module is invalid.");
                            }
                        }
                    }
                }
            }
            catch (DirectoryNotFoundException ex)
            {
                Middleware.WriteLogToFile(ex.Message);
            }
        }

        private OriginatorModule AddPositionOriginator(string FileName)
        {
            //Create a new assembly from the plugin file we're adding..
            Assembly pluginAssembly = Assembly.LoadFrom(FileName);

            //Next we'll loop through all the Types found in the assembly
            foreach (Type pluginType in pluginAssembly.GetTypes())
            {
                if (pluginType.IsPublic) //Only look at public types
                {
                    if (!pluginType.IsAbstract)  //Only look at non-abstract types
                    {
                        Type typeInterface = null;

                        //Gets a type object of the interface we need the plugins to match
                        foreach (Type t in pluginType.GetInterfaces())
                        {
                            if (t.FullName == "Interfaces.IPositionOriginator")
                                typeInterface = t;
                        }

                        //Make sure the interface we want to use actually exists
                        if (typeInterface != null)
                        {
                            //Create a new plugin since the type implements the IPositionOriginator interface
                            OriginatorModule newPlugin = new OriginatorModule();

                            //Set the filename where we found it
                            newPlugin.AssemblyPath = FileName;

                            //Create a new instance and store the instance in the collection for later use
                            newPlugin.Instance = (IPositionOriginator)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));

                            //Set the Plugin's host to this class which inherited IPluginMiddleware
                            newPlugin.Instance.Host = this;

                            //Call the initialization sub of the plugin
                            newPlugin.Instance.Initialize();

                            return (newPlugin);
                        }

                        typeInterface = null;
                    }
                }
            }
            pluginAssembly = null;

            return new OriginatorModule(false);
        }

        /// <summary>
        /// Assigns a weak (new) proximity signal to an absolute position (approx. edge of the cell)
        /// </summary>
        /// <param name="proximityData">Proximity signal data</param>
        private bool AssignNewSignal(IProximityData proximityData)
        {
            IAbsoluteData abs_data = AbsolutePositionCache.GetData(cacheInterval);
            if (abs_data != null)
            {
                // return pairedTable.UpdateBorder(proximityData, AbsolutePositionCache.GetGpsData(cacheInterval));
                return Middleware.Database.UpdateBorder(proximityData.HashValue, abs_data.Latitude, abs_data.Longitude, (short)proximityData.RelativeSignalStrength);
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Assigns a strong (best) proximity signal to an absolute position (approx. center of the cell)
        /// </summary>
        /// <param name="proximityData">Proximity signal data</param>
        private bool AssignBestSignal(IProximityData proximityData)
        {
            IAbsoluteData abs_data = AbsolutePositionCache.GetData(cacheInterval);
            if (abs_data != null)
            {
                // return pairedTable.UpdateCenter(proximityData, AbsolutePositionCache.GetGpsData(cacheInterval));
                return Middleware.Database.UpdateCenter(proximityData.HashValue, abs_data.Latitude, abs_data.Longitude, (short)proximityData.RelativeSignalStrength);
            }
            else
            {
                return false;
            }
        }

        private void NewSignalEventHandler(object sender, PositionEventArgs e)
        {
            List<PozrServer.Measurement> m_measurements = new List<Pozr.PozrServer.Measurement>();

            foreach (IProximityData item in e.Position)
            {
                if (AssignNewSignal(item))
                {
                    Middleware.PluginServices.OnPairedDataAvailable(sender, AbsolutePositionCache.GetData(2 * cacheInterval), item);
                    
                    PozrServer.Measurement m = new PozrServer.Measurement();
                    m.hash = item.HashValue;
                    m.m_type = false;
                    m.s_type = item.FormatCode;
                    m.lat = AbsolutePositionCache.Latitude;
                    m.lon = AbsolutePositionCache.Longitude;
                    m.strength = (short)item.RelativeSignalStrength;

                    m_measurements.Add(m);
                }
            }

            if (m_measurements.Count > 0)
            {
                Middleware.Server.SendRawMeasurement(m_measurements.ToArray());
            }

            if (((IPositionOriginator)sender).GetType() == PositionOriginator.Cell)
            {
                Middleware.PluginServices.OnNewCellDataAvailable(sender, ((CellData)e.Position[0]));
            }

            if (((IPositionOriginator)sender).GetType() == PositionOriginator.Wifi)
            {
                Middleware.PluginServices.OnNewWifiDataAvailable(sender, ((WifiData[])e.Position));
            }
        }

        private void BestSignalEventHandler(object sender, PositionEventArgs e)
        {
            List<PozrServer.Measurement> m_measurements = new List<Pozr.PozrServer.Measurement>();

            foreach (IProximityData item in e.Position)
            {
                if (AssignBestSignal(item))
                {
                    Middleware.PluginServices.OnPairedDataAvailable(sender, AbsolutePositionCache.GetData(2 * cacheInterval), item);

                    PozrServer.Measurement m = new PozrServer.Measurement();
                    m.hash = item.HashValue;
                    m.m_type = true;
                    m.s_type = item.FormatCode;
                    m.lat = AbsolutePositionCache.Latitude;
                    m.lon = AbsolutePositionCache.Longitude;
                    m.strength = (short)item.RelativeSignalStrength;

                    m_measurements.Add(m);

                    if (centerMarkerId < 0)
                    {
                        centerMarkerId = Middleware.Markers.AddProto(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Pozr.Resources.point.png"),
                                                 System.Drawing.Point.Empty,
                                                 System.Drawing.Point.Empty,
                                                 this.GetType().ToString());
                    }
                    Middleware.Markers.Add(m.lat, m.lon, centerMarkerId, "C", null);
                }
            }

            if (m_measurements.Count > 0)
            {
                Middleware.Server.SendRawMeasurement(m_measurements.ToArray());
            }
        }

        private void NewGpsSignalEventHandler(object sender, PositionEventArgs e)
        {
            if (((IPositionOriginator)sender).GetType() == PositionOriginator.Gps)
            {
                if (!Double.IsNaN(((GPSData)e.Position[0]).Latitude))
                {
                    Middleware.WriteLogToFile("New GPS data:\r\n Lat: " + ((GPSData)e.Position[0]).Latitude.ToString() + "\r\n Lon: " + ((GPSData)e.Position[0]).Longitude.ToString());
                    AbsolutePositionCache.Update((GPSData)e.Position[0]);
                    Middleware.PluginServices.OnNewAbsoluteDataAvailable(sender, (GPSData)e.Position[0]);
                }
                else
                {
                    /// dobni egy GpsSignalLost eventet
                }
            }
            else throw new InvalidOperationException("Invalid event!");
        }

        private void BestGpsSignalEventHandler(object sender, PositionEventArgs e)
        {
            if (((IPositionOriginator)sender).GetType() == PositionOriginator.Gps)
            {
            }
            else throw new InvalidOperationException("Invalid event!");
        }

        private void RequestedDataAvailableHandler(object sender, PositionEventArgs e)
        {
            switch (((IPositionOriginator)sender).GetType())
            {
                case PositionOriginator.Cell:
                    if (e.Position == null)
                    {
                        Middleware.WriteLogToFile("Requested Cell data: Unavailable!");
                    }
                    else
                    {
                        NewSignalEventHandler(this, e);
                        Middleware.WriteLogToFile("Requested Cell data:\r\n ID: " + ((CellData)e.Position[0]).Id.ToString() + " MNC: " + ((CellData)e.Position[0]).Mnc.ToString() + " Signal: " + ((CellData)e.Position[0]).SignalStrength.ToString());
                    }
                    break;
                case PositionOriginator.Gps:
                    if (e.Position == null)
                    {
                        Middleware.WriteLogToFile("Requested GPS data: Unavailable!");
                    }
                    else
                    {
                        Middleware.WriteLogToFile("Requested GPS data:\r\n Lat: " + ((GPSData)e.Position[0]).Latitude.ToString() + "\r\n Lon: " + ((GPSData)e.Position[0]).Longitude.ToString());
                    }
                    break;
                case PositionOriginator.Wifi:
                    string temp = "Requested Wifi data:\r\n";
                    if (e.Position == null)
                    {
                        temp += "Unavailable!";
                    }
                    else
                    {
                        foreach (WifiData wifiData in e.Position)
                        {
                            temp += wifiData.Name + " | " + BitConverter.ToString(wifiData.MacAddress) + " | " + wifiData.Rssi.ToString() + "\r\n";
                        }
                        NewSignalEventHandler(this, e);
                    }
                    Middleware.WriteLogToFile(temp);
                    break;
                default:
                    Middleware.WriteLogToFile("Invalid originator!");
                    break;
            }
        }

        private void RadioSwitchedOffEventHandler(object sender, EventArgs e)
        {
            Middleware.OnSignalChanged(sender as IPositionOriginator, false);
            
        }

        private void RadioSwitchedOnEventHandler(object sender, EventArgs e)
        {
            Middleware.OnSignalChanged(sender as IPositionOriginator, true);
        }

        public string[] GetCurrentContextHashes(PositionOriginator[] originators)
        {
            List<string> hashes = new List<string>();
            foreach (KeyValuePair<PositionOriginator, OriginatorModule> module in modules)
            {
                if (module.Value.Instance is IProximityOriginator && originators.Contains(module.Key) && module.Value.Instance.GetRadioStatus())
                {
                    hashes.AddRange((module.Value.Instance as IProximityOriginator).GetCurrentContextHashes());
                }
            }

            return hashes.ToArray();
        }

        #region ICommonInterface Members

        public bool InitSettingsGroup(string app_id)
        {
            return Middleware.Settings.InitSettingsGroup(app_id);
        }

        public bool DeleteSettingsGroup(string app_id)
        {
            return Middleware.Settings.DeleteSettingsGroup(app_id);
        }

        public bool SettingsGroupAvailable(string app_id)
        {
            return Middleware.Settings.SettingsGroupAvailable(app_id);
        }

        public bool SetSetting(string app_id, string setting_name, object data)
        {
            return Middleware.Settings.SetSetting(app_id, setting_name, data);
        }

        public bool DeleteSetting(string app_id, string setting_name)
        {
            return Middleware.Settings.DeleteSetting(app_id, setting_name);
        }

        public object GetSetting(string app_id, string setting_name)
        {
            return Middleware.Settings.GetSetting(app_id, setting_name);
        }

        #endregion

    }
}
